package com.lc.activiti.process.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lc.activiti.config.Result;
import com.lc.activiti.process.AuditBean;
import com.lc.activiti.process.AuditTools;
import com.lc.activiti.process.entity.TaskAuditRecords;
import com.lc.activiti.process.entity.TaskGoingFlow;
import com.lc.activiti.process.service.TaskActionService;
import com.lc.activiti.process.service.TaskAuditRecordsService;
import com.lc.activiti.process.service.TaskGoingFlowService;
import com.lc.activiti.sys.entity.*;
import com.lc.activiti.sys.service.*;
import com.lc.activiti.utils.JwtUtil;
import com.lc.activiti.utils.SecurityUtil;
import com.lc.activiti.utils.WebSocketServer;
import com.lc.activiti.utils.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 流程任务线路记录(TaskGoingFlow)表服务实现类
 *
 * @author 骆超
 * @since 2021-06-16 16:51:54
 */
@Service("taskActionService")
@Slf4j
public class TaskActionServiceImpl implements TaskActionService {
    @Resource
    private ProcessRuntime processRuntime;
    @Resource
    private SecurityUtil securityUtil;
    @Resource
    private TaskService taskService;
    @Resource
    private SysAuditService sysAuditService;
    @Resource
    private TaskGoingFlowService taskGoingFlowService;
    @Resource
    private TaskAuditRecordsService auditRecordsService;
    @Resource
    private TaskRuntime taskRuntime;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private HistoryService historyService;
    @Resource
    private SysUserRoleService userRoleService;
    @Resource
    private SysRoleService roleService;
    @Resource
    private SysUserService userService;
    @Resource
    private SysMessageService sysMessageService;


    /**
     * 业务流程提交通用
     *
     * @param businessId           业务相关id
     * @param processDefinitionKey 流程标识的key
     * @param processName          流程名称
     * @return com.lc.activiti.config.Result
     * @author 骆超
     * @date 2021/6/22
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result startTaskByBusiness(Long businessId, String processDefinitionKey, String processName, JSONObject jsonObject) throws IOException {
        ProcessInstance processInstance = null;
        try {
            securityUtil.logInAs();
            String nowStr = DateUtil.format(DateUtil.date(), DatePattern.NORM_DATETIME_PATTERN);
            SysUser user = JwtUtil.getUser();
            String str = jsonObject.getStr("details");
            processInstance = processRuntime.start(ProcessPayloadBuilder
                    .start()
                    .withProcessDefinitionKey(processDefinitionKey)
                    .withName("【" + processName + "】 - " + user.getName() + " - " + nowStr)
                    .withVariables(jsonObject)
                    .withBusinessKey(businessId + "")
                    .build());
            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
            taskGoingFlowService.saveGoingFlow(task, "start");

            //审核人id集合
            List<String> userIds = ObjectUtil.isNotEmpty(task)?this.getUserIds(task, processDefinitionKey, null):new ArrayList<>();
            //保存流程实例与业务的关联
            sysAuditService.save(
                    new SysAudit()
                            .setName(processInstance.getName())
                            .setProcessDefinitionId(processInstance.getProcessDefinitionId())
                            .setStatus(1)
                            .setProcessInstanceId(processInstance.getId())
                            .setProcessDefinitionKey(processInstance.getProcessDefinitionKey())
                            .setAuditor(RedisUtil.getUserName(String.join(",", userIds)))
                            .setCurrentNode(task.getName())
                            .setRemark(ObjectUtil.isNotEmpty(str) ? str : "")
                            .setBusinessId(businessId)

            );
            //保存一个审核记录的对象
            auditRecordsService.save(
                    new TaskAuditRecords()
                            .setBusinessId(businessId)
                            .setAuditor(user.getName())
                            .setProcessInstanceId(processInstance.getId())
                            .setTaskNode("发起流程"));
            //推送消息
            List<SysMessage> messages = new ArrayList<>();
            userIds.forEach(userId -> {
                messages.add(new SysMessage()
                        .setTitle("【" + user.getName() + "】 的 " + "【" + processName + "】" + nowStr)
                        .setRecipient(Long.valueOf(userId))
                        .setContent(ObjectUtil.isNotEmpty(str) ? str : "")
                        .setMsgType(1L)
                        .setSender(user.getId()));
            });
            sysMessageService.saveBatch(messages);
            messages.forEach(msg -> {
                WebSocketServer.sendByUserId("msg", msg, msg.getRecipient());
            });
            return Result.success(processInstance).setMsg(processName + "提交成功！");
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 任务审核
     *
     * @param auditBean 参数说明
     * @return com.lc.activiti.config.Result
     * @author 骆超
     * @date 2021/6/25
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result auditTask(@RequestBody AuditBean auditBean) throws Exception {
        Task task = taskService.createTaskQuery().processInstanceId(auditBean.getProcessInstanceId()).singleResult();
        JSONObject variables = ObjectUtil.isEmpty(auditBean.getVariables()) ? new JSONObject() : auditBean.getVariables();
        String userId = String.valueOf(JwtUtil.getUser().getId());
        //任务审核
        AuditTools.audit(task, variables, auditBean.getProcessType());
        Task nextTask = taskService.createTaskQuery().processInstanceId(auditBean.getProcessInstanceId()).singleResult();
        //找到审核表中的记录
        SysAudit sysAudit = sysAuditService.getOne(Wrappers.lambdaQuery(SysAudit.class).eq(SysAudit::getEnable, 1).eq(SysAudit::getProcessInstanceId, auditBean.getProcessInstanceId()));
        List<String> userIds = ObjectUtil.isNotEmpty(nextTask)?this.getUserIds(nextTask, auditBean.getDefinitionKey(), sysAudit):new ArrayList<>();
        //实例化一个审核记录的对象
        TaskAuditRecords auditRecords = new TaskAuditRecords()
                .setBusinessId(sysAudit.getBusinessId())
                .setAuditOpinion(auditBean.getProcessDescription())
                .setAuditor(RedisUtil.getUserName(userId))
                .setProcessInstanceId(auditBean.getProcessInstanceId())
                .setAuditResult(Integer.valueOf(auditBean.getProcessType()))
                .setTaskNode(task.getName());
        auditRecords.insert();
        if (ObjectUtil.isNotEmpty(nextTask)) {
            ProcessInstance processInstance = processRuntime.processInstance(auditBean.getProcessInstanceId());
            sysAudit.setStatus(ObjectUtil.isEmpty(processInstance) ? 2 : 1);
            sysAudit.setCurrentNode(nextTask.getName());
            sysAudit.setAuditor(RedisUtil.getUserName(String.join(",", userIds)));
        } else {
            sysAudit.setStatus("0".equals(auditBean.getProcessType()) ? 3 : 2);
            sysAudit.setCurrentNode("审核结束");
            sysAudit.setAuditor(null);
            auditRecordsService.save(
                    new TaskAuditRecords()
                            .setBusinessId(sysAudit.getBusinessId())
                            .setProcessInstanceId(auditBean.getProcessInstanceId())
                            .setTaskNode("审核结束"));
        }
        sysAuditService.updateById(sysAudit);
        return Result.success(sysAudit);
    }

    /**
     * 获取流程图
     *
     * @param response  参数说明
     * @param auditBean 参数说明
     * @author 骆超
     * @date 2021/6/25
     */
    @Override
    public void getAuditedImg(HttpServletResponse response, AuditBean auditBean) throws IOException {
        //找出所有已进行的节点
        List<HistoricActivityInstance> activityInstances =
                historyService.createHistoricActivityInstanceQuery()
                        .processInstanceId(auditBean.getProcessInstanceId())
                        .orderByHistoricActivityInstanceStartTime()
                        .desc().list();
        //获取流程模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(auditBean.getProcessDefinitionId());
        //初始化一个 高亮线的集合
        List<String> highLightedFlows = new ArrayList<>();
        //对流程中已进行的节点进行遍历
        AtomicReference<String> lastLight = new AtomicReference<>("");
        List<TaskGoingFlow> flowList = taskGoingFlowService.list(Wrappers.lambdaQuery(TaskGoingFlow.class).eq(TaskGoingFlow::getDelFlag, 1).eq(TaskGoingFlow::getProcessInstanceId, auditBean.getProcessInstanceId()));
        //将已进行过的节点id取出放在集合中
        List<String> highLightedActivities = activityInstances.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
        highLightedActivities = highLightedActivities.stream().filter(a -> flowList.stream().anyMatch(f -> (ObjectUtil.isNotEmpty(f.getTargetRef()) && f.getTargetRef().equals(a)) || ObjectUtil.isNotEmpty(f.getSourceRef()) && f.getSourceRef().equals(a))).collect(Collectors.toList());
        //用来生成流程图片的对象
        DefaultProcessDiagramGenerator defaultProcessDiagramGenerator = new DefaultProcessDiagramGenerator();
        //防止图片乱码
        InputStream in = defaultProcessDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities,
                flowList.stream().map(TaskGoingFlow::getGoingFlow).collect(Collectors.toList()), "宋体",
                "宋体", "宋体", true, null);
        response.setContentType("image/png;charset=UTF-8");
        convertToPng(IoUtil.readUtf8(in), response.getOutputStream());
    }


    /**
     * 将svg字符串转为png文件流返回
     *
     * @param svgStr       参数说明
     * @param outputStream 参数说明
     * @author 骆超
     * @date 2021/6/25
     */
    private void convertToPng(String svgStr, OutputStream outputStream) throws IOException {
        try {
            PNGTranscoder t = new PNGTranscoder();
            TranscoderInput input = new TranscoderInput(IoUtil.toUtf8Stream(svgStr));
            TranscoderOutput output = new TranscoderOutput(outputStream);
            t.transcode(input, output);
        } catch (TranscoderException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
        }
    }

    /**
     * 获取审核列表
     *
     * @param auditBean 参数说明
     * @return com.lc.activiti.config.Result
     * @author 骆超
     * @date 2021/6/25
     */
    @Override
    public Result getTasks(AuditBean auditBean) {
        Page<SysAudit> page = ObjectUtil.isNull(auditBean.getPage()) ? new Page<>() : auditBean.getPage();
        //认证
        securityUtil.logInAs();
        //获取当前登录人的id
        String userId = JwtUtil.getUser().getId().toString();
        List<String> processInstanceIds = new ArrayList<>();
        //找到用户角色的关联
        List<SysUserRole> list = userRoleService.list(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, userId));
        //找到分组角色
        List<SysRole> groupRoles = roleService.list(Wrappers.lambdaQuery(SysRole.class).eq(SysRole::getRoleType, 1).in(SysRole::getId, list.stream().map(SysUserRole::getRoleId).collect(Collectors.toList())));
        List<Long> longList = list.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
        if (!groupRoles.isEmpty()) {
            longList.removeAll(groupRoles.stream().map(SysRole::getId).collect(Collectors.toList()));
        }
        //判断是否审核过
        if ("yes".equals(auditBean.getIsProcessed())) {
            List<HistoricActivityInstance> historicList = historyService.createHistoricActivityInstanceQuery().taskAssignee(userId).finished().orderByHistoricActivityInstanceEndTime().desc().list();
            processInstanceIds = historicList.stream().map(HistoricActivityInstance::getProcessInstanceId).collect(Collectors.toList());
        } else {
            TaskQuery taskQuery = taskService.createTaskQuery().taskCandidateOrAssigned(userId).processDefinitionKey(auditBean.getDefinitionKey());
            //人员和通用角色下的维度的任务
            if (!longList.isEmpty()) {
                taskQuery.taskCandidateGroupIn(longList.stream().map(String::valueOf).collect(Collectors.toList()));
            }
            List<Task> tasks = taskQuery.list();
            processInstanceIds = tasks.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
            if (!groupRoles.isEmpty()) {
                //分组角色维度下的任务
                List<Task> rolesTasks = taskService.createTaskQuery().taskCandidateGroupIn(groupRoles.stream().map(m -> String.valueOf(m.getId())).collect(Collectors.toList())).processDefinitionKey(auditBean.getDefinitionKey()).list();
                //寻找到自己的部门的其他成员
                List<SysUser> userList = userService.list(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getDeptId, JwtUtil.getUser().getDeptId()));
                if (!rolesTasks.isEmpty() && !userList.isEmpty()) {
                    //找到在当前的分组角色下,且属于本部门成员提交的流程记录
                    List<SysAudit> sysAuditList = sysAuditService.list(Wrappers.lambdaQuery(SysAudit.class).in(SysAudit::getCreateUser, userList.stream().map(SysUser::getId).collect(Collectors.toList())).in(SysAudit::getProcessInstanceId, rolesTasks.stream().map(Task::getProcessInstanceId).collect(Collectors.toList())));
                    if (!sysAuditList.isEmpty()) {
                        processInstanceIds.addAll(sysAuditList.stream().map(SysAudit::getProcessInstanceId).collect(Collectors.toList()));
                    }
                }
            }
        }
        Page<SysAudit> sysAuditPage = sysAuditService.page(page, Wrappers.lambdaQuery(SysAudit.class).like(ObjectUtil.isNotEmpty(auditBean.getTaskName()), SysAudit::getName, auditBean.getTaskName()).eq(processInstanceIds.isEmpty(), SysAudit::getId, -1).in(!processInstanceIds.isEmpty(), SysAudit::getProcessInstanceId, processInstanceIds).orderByDesc(SysAudit::getCreateDate));
        return Result.success(sysAuditPage);
    }

    /**
     * 任务转办
     *
     * @param auditBean 参数说明
     * @return com.lc.activiti.config.Result
     * @author 骆超
     * @date 2021/6/25
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result transferTask(AuditBean auditBean) throws Exception {
        try {
            Task task = taskService.createTaskQuery().processInstanceId(auditBean.getProcessInstanceId()).singleResult();
            String userId = String.valueOf(JwtUtil.getUser().getId());
            //找到审核表中的记录
            SysAudit sysAudit = sysAuditService.getOne(Wrappers.lambdaQuery(SysAudit.class).eq(SysAudit::getEnable, 1).eq(SysAudit::getProcessInstanceId, auditBean.getProcessInstanceId()));
            String newAssignee = RedisUtil.getUserName(auditBean.getAssignee());
            //实例化一个审核记录的对象
            auditRecordsService.save(new TaskAuditRecords()
                    .setBusinessId(sysAudit.getBusinessId())
                    .setAuditOpinion("【" + JwtUtil.getUser().getName() + "】将" + "--" + task.getName() + "--阶段任务转交由【" + newAssignee + "】办理")
                    .setAuditor(RedisUtil.getUserName(userId))
                    .setProcessInstanceId(auditBean.getProcessInstanceId())
                    .setAuditResult(2)
                    .setTaskNode(task.getName()));
            taskService.setAssignee(task.getId(), auditBean.getAssignee());
            sysAudit.setAuditor(newAssignee).updateById();
            return Result.success(sysAudit);
        } catch (Exception e) {
            throw new Exception("任务转办失败！");
        }
    }

    /**
     * 流程撤回
     *
     * @param auditBean 参数说明
     * @return com.lc.activiti.config.Result
     * @author 骆超
     * @date 2021/7/1
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result recallProcess(AuditBean auditBean) throws Exception {
        try {
            Task task = taskService.createTaskQuery().processInstanceId(auditBean.getProcessInstanceId()).singleResult();
            String userId = String.valueOf(JwtUtil.getUser().getId());
            //找到审核表中的记录
            SysAudit sysAudit = sysAuditService.getOne(Wrappers.lambdaQuery(SysAudit.class).eq(SysAudit::getEnable, 1).eq(SysAudit::getProcessInstanceId, auditBean.getProcessInstanceId()));
            //实例化一个审核记录的对象
            auditRecordsService.save(new TaskAuditRecords()
                    .setBusinessId(sysAudit.getBusinessId())
                    .setAuditOpinion("【" + JwtUtil.getUser().getName() + "】于" + task.getName() + "--阶段撤回了流程")
                    .setAuditor(RedisUtil.getUserName(userId))
                    .setProcessInstanceId(auditBean.getProcessInstanceId())
                    .setAuditResult(2)
                    .setTaskNode(task.getName()));
            JSONObject variables = ObjectUtil.isEmpty(auditBean.getVariables()) ? new JSONObject() : auditBean.getVariables();
            AuditTools.audit(task, variables, "-1");
            sysAudit.setStatus(0);
            sysAudit.setCurrentNode("流程撤回");
            sysAudit.setAuditor("");
            sysAudit.updateById();
            return Result.success(sysAudit);
        } catch (Exception e) {
            throw new Exception("流程撤回失败！");
        }
    }


    private List<String> getUserIds(Task task, String processDefinitionKey, SysAudit sysAudit) {
        //获取流程定义集合（以版本倒序排序）
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).orderByProcessDefinitionVersion().desc().list();
        //第一个为最新的流程模型，找的流程图模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(list.get(0).getId());
        //获取流程中的节点
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        List<String> userIds = new ArrayList<>();
        //找出用户任务节点，看似循环，实际只存在一个节点，只执行一次
        flowElements.stream().filter(f -> f instanceof UserTask && f.getId().equals(task.getTaskDefinitionKey())).findFirst().ifPresent(m -> {
            UserTask userTask = (UserTask) m;
            if (ObjectUtil.isNotEmpty(userTask.getAssignee())) {
                userIds.add(userTask.getAssignee());
            } else if (ObjectUtil.isNotEmpty(userTask.getCandidateUsers())) {
                userIds.addAll(userTask.getCandidateUsers());
            } else if (ObjectUtil.isNotEmpty(userTask.getCandidateGroups())) {
                //将该任务所有的角色的记录克隆
                List<String> roleList = ObjectUtil.clone(userTask.getCandidateGroups());
                //找出拥有分组角色的记录
                List<SysUserRole> userRoles = userRoleService.list(Wrappers.lambdaQuery(SysUserRole.class).in(SysUserRole::getRoleId, userTask.getCandidateGroups()));
                //找到分组角色
                List<SysRole> groupRoles = roleService.list(Wrappers.lambdaQuery(SysRole.class).eq(SysRole::getRoleType, 1).in(SysRole::getId, userTask.getCandidateGroups()));
                if (!groupRoles.isEmpty()) {
                    //利用分组用户角色的关系，找到审核任务中具有该角色的人员id集合
                    List<String> userIdList = userRoles.stream().filter(userRole -> groupRoles.stream().map(SysRole::getId).collect(Collectors.toList()).contains(userRole.getRoleId())).map(u -> String.valueOf(u.getUserId())).collect(Collectors.toList());
                    Long deptId = ObjectUtil.isNotNull(sysAudit) ? userService.getById(sysAudit.getCreateUser()).getDeptId() : JwtUtil.getUser().getDeptId();
                    //找出分组角色中是和任务发起者相同部门的那些用户
                    List<SysUser> sysUsers = userService.list(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getDelFlag, 1).eq(SysUser::getDeptId, ObjectUtil.isNotNull(deptId) ? deptId : JwtUtil.getUser().getDeptId()).in(SysUser::getId, userIdList));
                    if (!sysUsers.isEmpty()) {
                        userIds.addAll(sysUsers.stream().map(u -> String.valueOf(u.getId())).collect(Collectors.toList()));
                    }
                    //移除角色中属于分组角色的那部分
                    roleList.removeAll(groupRoles.stream().map(gr -> String.valueOf(gr.getId())).collect(Collectors.toList()));
                }
                //剩下的通用角色的记录，将拥有这些角色的人们添加至集合中
                userIds.addAll(userRoles.stream().filter(a -> roleList.contains(String.valueOf(a.getRoleId()))).map(role -> String.valueOf(role.getUserId())).collect(Collectors.toList()));
            }
        });
        return userIds;
    }
}
